import java.util.HashMap;
import java.util.Map;

import com.adventnet.snmp.snmp2.SnmpAPI;
import com.adventnet.snmp.snmp2.SnmpException;
import com.adventnet.snmp.snmp2.SnmpOID;
import com.adventnet.snmp.snmp2.SnmpPDU;
import com.adventnet.snmp.snmp2.SnmpSession;
import com.adventnet.snmp.snmp2.SnmpVar;
import com.adventnet.snmp.snmp2.SnmpVarBind;
import com.adventnet.snmp.snmp2.UDPProtocolOptions;
import com.adventnet.snmp.snmp2.usm.USMUserEntry;
import com.adventnet.snmp.snmp2.usm.USMUtils;

/**
 * SNMPUtils handles the SNMP specific operations
 * @author skinner@kth.se
 * @author aidi@kth.se
 */
class SNMPUtils {
	
	/* User credentials for SNMPv3 */
	public final static String RTR_USERNAME	= "2G1332_student";
	public final static String RTR_PASSWORD	= "netmanagement";
	
	/*
	 * OIDs that we have made use of in developing this project *indicates we are currently using them
	 */
	public static final Map<String,String> OIDS = new HashMap<String, String>();
	static {
		OIDS.put("sysName", 		".1.3.6.1.2.1.1.5");		//* An administratively assigned name for this managed Node. By convention, this is the Node's fully	qualified domain name.
		OIDS.put("ifIndex", 		".1.3.6.1.2.1.2.2.1.1");	//* A unique value for each interface. Its value ranges between 1 and the value of ifNumber. The value for each interface must remain constant at least from one reinitialization of the entity's network management system to the next reinitialization.
		OIDS.put("ifDescr", 		".1.3.6.1.2.1.2.2.1.2");	//* A textual string containing information about the interface. This string should include the name of the manufacturer, the product name, and the version of the hardware interface.
		OIDS.put("ifInOctets", 		".1.3.6.1.2.1.2.2.1.10");	//* The total number of octets received on the interface, including framing characters
		OIDS.put("ifInUcastPkts", 	".1.3.6.1.2.1.2.2.1.11");	//* The number of subnetwork-unicast packets delivered to a higher-layer protocol.
		OIDS.put("ifInNUcastPkts", 	".1.3.6.1.2.1.2.2.1.12");	//  Number of non-unicast (that is, subnetwork- broadcast or subnetwork-multicast) packets delivered to a higher-layer protocol.
		OIDS.put("ipAdEntAddr", 	".1.3.6.1.2.1.4.20.1.1");	//* The IP address to which this entry's addressing information pertains.
		OIDS.put("ipAdEntIfIndex",	".1.3.6.1.2.1.4.20.1.2");	//  The index value which uniquely identifies the interface to which this entry is applicable. The interface identified by a particular value of this index is the same interface as identified by the same value of ifIndex.
		OIDS.put("ipRouteNextHop",	".1.3.6.1.2.1.4.21.1.7");	//* The IP address of the next hop of this route. In the case of a route bound to an interface which is realized from a broadcast media, the value of this field is the agent's IP address on that interface.
		OIDS.put("tcpPassiveOpens", ".1.3.6.1.2.1.6.6");		//  The number of times TCP connections have made a direct transition to the SYN-RCVD state from the LISTEN state.
		OIDS.put("tcpInSegs",		".1.3.6.1.2.1.6.10");		//  The total number of segments received, including those received in error. This count includes segments received on currently established connections.
	}
	
	/* Variables needed for SNMP operations */
	private SnmpSession session;
	private SnmpAPI api;
	
	/**
	 * Constructor to begin SNMP functions
	 */
	public SNMPUtils() {
		// Start the SNMP API
		api = new SnmpAPI();
		//api.setDebug(true);
	}
	
	/**
	 * Opens SNMP session
	 */
	public void open() {
		session = new SnmpSession(api);
		try {
			session.open();
			session.setVersion(3);
		} catch (SnmpException e) {
			e.printStackTrace();
			System.err.println("FAIL on session - SNMPException!");
			System.exit(-1);
		}
	}
	
	/**
	 * Closes the SNMP session
	 */
	public void close() {
		session.close();
		api.close();
	}
	
	/**
	 * 
	 * @param type 		-	Type of SNMP message
	 * @param ip		-	IP address destination of pdu 
	 * @param oidStr	-	Which object to return, specified by OID string
	 * @return
	 */
	public SnmpVarBind getVarBind( byte type, String ip, String oidStr ) {
		SnmpPDU pdu = new SnmpPDU();
		UDPProtocolOptions option = new UDPProtocolOptions(ip);
		pdu.setProtocolOptions(option); //sets the host
		pdu.setCommand(type); 
		
		// Authentication testing...
		pdu.setUserName(RTR_USERNAME.getBytes());
		try {
			USMUtils.init_v3_parameters(
				RTR_USERNAME, 
				USMUserEntry.MD5_AUTH, 
				RTR_PASSWORD,  
				null,
				option.getRemoteHost(), 
				option.getRemotePort(), 
				session);
		} catch (SnmpException e) {
			e.printStackTrace();
		}
		
		SnmpOID oid = new SnmpOID(oidStr);
		pdu.addNull(oid);
		
		SnmpPDU result;
		try {
			result = session.syncSend(pdu);
			if (result == null ) {
				// Output that we got null here, but throw the exception so we can handle it depending on the situation
				System.out.println("RESULT IS NULL!!! TIMEOUT!!!\tIP: " + ip + "\tOID: " + oidStr);
			}
			return (SnmpVarBind) result.getVariableBinding(0);
			
		} catch (SnmpException e) {
			e.printStackTrace();
			System.out.println("SNMP EXCEPTION ON\tIP: " + ip + "\tOID: " + oidStr);
		}	
		
		// This should only happen on an SNMPException - which will likely be fatal to program execution
		return null;
	}
	
	/**
	 * Since we use GETNEXT most of the time
	 * @param ip		-	IP address destination of pdu 
	 * @param oidStr	-	Which object to return, specified by OID string
	 * @return
	 */
	public SnmpVarBind getVarBind( String ip, String oidStr ) {
		return getVarBind(SnmpAPI.GETNEXT_REQ_MSG, ip, oidStr);
	}
	
	/**
	 * When we don't care about the OID, and just want the variable
	 * @param ip		-	IP address destination of pdu 
	 * @param oidStr	-	Which object to return, specified by OID string
	 * @return
	 */
	public SnmpVar getVar( String ip, String oidStr) {
		return getVarBind(SnmpAPI.GETNEXT_REQ_MSG, ip, oidStr).getVariable();
	}
}
